// dnlib: See LICENSE.txt for more info

namespace dnlib.DotNet.Emit {
	/// <summary>
	/// A CIL opcode. If the high byte is 0 or if it's <see cref="UNKNOWN1"/>, it's a 1-byte opcode,
	/// else it's a two-byte opcode and the highest byte is the first byte of the opcode.
	/// </summary>
	public enum Code : ushort {
#pragma warning disable 1591	// disable XML doc warning
		UNKNOWN1		= 0x0100,
		UNKNOWN2		= 0x0101,
		Add				= 0x0058,
		Add_Ovf			= 0x00D6,
		Add_Ovf_Un		= 0x00D7,
		And				= 0x005F,
		Arglist			= 0xFE00,
		Beq				= 0x003B,
		Beq_S			= 0x002E,
		Bge				= 0x003C,
		Bge_S			= 0x002F,
		Bge_Un			= 0x0041,
		Bge_Un_S		= 0x0034,
		Bgt				= 0x003D,
		Bgt_S			= 0x0030,
		Bgt_Un			= 0x0042,
		Bgt_Un_S		= 0x0035,
		Ble				= 0x003E,
		Ble_S			= 0x0031,
		Ble_Un			= 0x0043,
		Ble_Un_S		= 0x0036,
		Blt				= 0x003F,
		Blt_S			= 0x0032,
		Blt_Un			= 0x0044,
		Blt_Un_S		= 0x0037,
		Bne_Un			= 0x0040,
		Bne_Un_S		= 0x0033,
		Box				= 0x008C,
		Br				= 0x0038,
		Br_S			= 0x002B,
		Break			= 0x0001,
		Brfalse			= 0x0039,
		Brfalse_S		= 0x002C,
		Brtrue			= 0x003A,
		Brtrue_S		= 0x002D,
		Call			= 0x0028,
		Calli			= 0x0029,
		Callvirt		= 0x006F,
		Castclass		= 0x0074,
		Ceq				= 0xFE01,
		Cgt				= 0xFE02,
		Cgt_Un			= 0xFE03,
		Ckfinite		= 0x00C3,
		Clt				= 0xFE04,
		Clt_Un			= 0xFE05,
		Constrained		= 0xFE16,
		Conv_I			= 0x00D3,
		Conv_I1			= 0x0067,
		Conv_I2			= 0x0068,
		Conv_I4			= 0x0069,
		Conv_I8			= 0x006A,
		Conv_Ovf_I		= 0x00D4,
		Conv_Ovf_I_Un	= 0x008A,
		Conv_Ovf_I1		= 0x00B3,
		Conv_Ovf_I1_Un	= 0x0082,
		Conv_Ovf_I2		= 0x00B5,
		Conv_Ovf_I2_Un	= 0x0083,
		Conv_Ovf_I4		= 0x00B7,
		Conv_Ovf_I4_Un	= 0x0084,
		Conv_Ovf_I8		= 0x00B9,
		Conv_Ovf_I8_Un	= 0x0085,
		Conv_Ovf_U		= 0x00D5,
		Conv_Ovf_U_Un	= 0x008B,
		Conv_Ovf_U1		= 0x00B4,
		Conv_Ovf_U1_Un	= 0x0086,
		Conv_Ovf_U2		= 0x00B6,
		Conv_Ovf_U2_Un	= 0x0087,
		Conv_Ovf_U4		= 0x00B8,
		Conv_Ovf_U4_Un	= 0x0088,
		Conv_Ovf_U8		= 0x00BA,
		Conv_Ovf_U8_Un	= 0x0089,
		Conv_R_Un		= 0x0076,
		Conv_R4			= 0x006B,
		Conv_R8			= 0x006C,
		Conv_U			= 0x00E0,
		Conv_U1			= 0x00D2,
		Conv_U2			= 0x00D1,
		Conv_U4			= 0x006D,
		Conv_U8			= 0x006E,
		Cpblk			= 0xFE17,
		Cpobj			= 0x0070,
		Div				= 0x005B,
		Div_Un			= 0x005C,
		Dup				= 0x0025,
		Endfilter		= 0xFE11,
		Endfinally		= 0x00DC,
		Initblk			= 0xFE18,
		Initobj			= 0xFE15,
		Isinst			= 0x0075,
		Jmp				= 0x0027,
		Ldarg			= 0xFE09,
		Ldarg_0			= 0x0002,
		Ldarg_1			= 0x0003,
		Ldarg_2			= 0x0004,
		Ldarg_3			= 0x0005,
		Ldarg_S			= 0x000E,
		Ldarga			= 0xFE0A,
		Ldarga_S		= 0x000F,
		Ldc_I4			= 0x0020,
		Ldc_I4_0		= 0x0016,
		Ldc_I4_1		= 0x0017,
		Ldc_I4_2		= 0x0018,
		Ldc_I4_3		= 0x0019,
		Ldc_I4_4		= 0x001A,
		Ldc_I4_5		= 0x001B,
		Ldc_I4_6		= 0x001C,
		Ldc_I4_7		= 0x001D,
		Ldc_I4_8		= 0x001E,
		Ldc_I4_M1		= 0x0015,
		Ldc_I4_S		= 0x001F,
		Ldc_I8			= 0x0021,
		Ldc_R4			= 0x0022,
		Ldc_R8			= 0x0023,
		Ldelem			= 0x00A3,
		Ldelem_I		= 0x0097,
		Ldelem_I1		= 0x0090,
		Ldelem_I2		= 0x0092,
		Ldelem_I4		= 0x0094,
		Ldelem_I8		= 0x0096,
		Ldelem_R4		= 0x0098,
		Ldelem_R8		= 0x0099,
		Ldelem_Ref		= 0x009A,
		Ldelem_U1		= 0x0091,
		Ldelem_U2		= 0x0093,
		Ldelem_U4		= 0x0095,
		Ldelema			= 0x008F,
		Ldfld			= 0x007B,
		Ldflda			= 0x007C,
		Ldftn			= 0xFE06,
		Ldind_I			= 0x004D,
		Ldind_I1		= 0x0046,
		Ldind_I2		= 0x0048,
		Ldind_I4		= 0x004A,
		Ldind_I8		= 0x004C,
		Ldind_R4		= 0x004E,
		Ldind_R8		= 0x004F,
		Ldind_Ref		= 0x0050,
		Ldind_U1		= 0x0047,
		Ldind_U2		= 0x0049,
		Ldind_U4		= 0x004B,
		Ldlen			= 0x008E,
		Ldloc			= 0xFE0C,
		Ldloc_0			= 0x0006,
		Ldloc_1			= 0x0007,
		Ldloc_2			= 0x0008,
		Ldloc_3			= 0x0009,
		Ldloc_S			= 0x0011,
		Ldloca			= 0xFE0D,
		Ldloca_S		= 0x0012,
		Ldnull			= 0x0014,
		Ldobj			= 0x0071,
		Ldsfld			= 0x007E,
		Ldsflda			= 0x007F,
		Ldstr			= 0x0072,
		Ldtoken			= 0x00D0,
		Ldvirtftn		= 0xFE07,
		Leave			= 0x00DD,
		Leave_S			= 0x00DE,
		Localloc		= 0xFE0F,
		Mkrefany		= 0x00C6,
		Mul				= 0x005A,
		Mul_Ovf			= 0x00D8,
		Mul_Ovf_Un		= 0x00D9,
		Neg				= 0x0065,
		Newarr			= 0x008D,
		Newobj			= 0x0073,
		No				= 0xFE19,
		Nop				= 0x0000,
		Not				= 0x0066,
		Or				= 0x0060,
		Pop				= 0x0026,
		Prefix1			= 0x00FE,
		Prefix2			= 0x00FD,
		Prefix3			= 0x00FC,
		Prefix4			= 0x00FB,
		Prefix5			= 0x00FA,
		Prefix6			= 0x00F9,
		Prefix7			= 0x00F8,
		Prefixref		= 0x00FF,
		Readonly		= 0xFE1E,
		Refanytype		= 0xFE1D,
		Refanyval		= 0x00C2,
		Rem				= 0x005D,
		Rem_Un			= 0x005E,
		Ret				= 0x002A,
		Rethrow			= 0xFE1A,
		Shl				= 0x0062,
		Shr				= 0x0063,
		Shr_Un			= 0x0064,
		Sizeof			= 0xFE1C,
		Starg			= 0xFE0B,
		Starg_S			= 0x0010,
		Stelem			= 0x00A4,
		Stelem_I		= 0x009B,
		Stelem_I1		= 0x009C,
		Stelem_I2		= 0x009D,
		Stelem_I4		= 0x009E,
		Stelem_I8		= 0x009F,
		Stelem_R4		= 0x00A0,
		Stelem_R8		= 0x00A1,
		Stelem_Ref		= 0x00A2,
		Stfld			= 0x007D,
		Stind_I			= 0x00DF,
		Stind_I1		= 0x0052,
		Stind_I2		= 0x0053,
		Stind_I4		= 0x0054,
		Stind_I8		= 0x0055,
		Stind_R4		= 0x0056,
		Stind_R8		= 0x0057,
		Stind_Ref		= 0x0051,
		Stloc			= 0xFE0E,
		Stloc_0			= 0x000A,
		Stloc_1			= 0x000B,
		Stloc_2			= 0x000C,
		Stloc_3			= 0x000D,
		Stloc_S			= 0x0013,
		Stobj			= 0x0081,
		Stsfld			= 0x0080,
		Sub				= 0x0059,
		Sub_Ovf			= 0x00DA,
		Sub_Ovf_Un		= 0x00DB,
		Switch			= 0x0045,
		Tailcall		= 0xFE14,
		Throw			= 0x007A,
		Unaligned		= 0xFE12,
		Unbox			= 0x0079,
		Unbox_Any		= 0x00A5,
		Volatile		= 0xFE13,
		Xor				= 0x0061,
#pragma warning restore
	}

	public static partial class Extensions {
		/// <summary>
		/// Determines whether a <see cref="Code"/> is experimental
		/// </summary>
		/// <param name="code">The code</param>
		/// <returns><c>true</c> if the <see cref="Code"/> is experimental; otherwise, <c>false</c></returns>
		public static bool IsExperimental(this Code code) {
			byte hi = (byte)((ushort)code >> 8);

			return hi >= 0xF0 && hi <= 0xFB;
		}

		/// <summary>
		/// Converts a <see cref="Code"/> to an <see cref="OpCode"/>
		/// </summary>
		/// <param name="code">The code</param>
		/// <returns>A <see cref="OpCode"/> or <c>null</c> if it's invalid</returns>
		public static OpCode ToOpCode(this Code code) {
			byte hi = (byte)((ushort)code >> 8);
			byte lo = (byte)code;
			if (hi == 0)
				return OpCodes.OneByteOpCodes[lo];
			if (hi == 0xFE)
				return OpCodes.TwoByteOpCodes[lo];
			if (code == Code.UNKNOWN1)
				return OpCodes.UNKNOWN1;
			if (code == Code.UNKNOWN2)
				return OpCodes.UNKNOWN2;
			return null;
		}

		/// <summary>
		/// Converts a <see cref="Code"/> to an <see cref="OpCode"/>, using a module context to look
		/// up potential experimental opcodes
		/// </summary>
		/// <param name="code">The code</param>
		/// <param name="context">The module context</param>
		/// <returns>A <see cref="OpCode"/> or <c>null</c> if it's invalid</returns>
		public static OpCode ToOpCode(this Code code, ModuleContext context) {
			byte hi = (byte)((ushort)code >> 8);
			byte lo = (byte)code;
			if (hi == 0)
				return OpCodes.OneByteOpCodes[lo];
			if (hi == 0xFE)
				return OpCodes.TwoByteOpCodes[lo];
			if (context.GetExperimentalOpCode(hi, lo) is OpCode op)
				return op;
			if (code == Code.UNKNOWN1)
				return OpCodes.UNKNOWN1;
			if (code == Code.UNKNOWN2)
				return OpCodes.UNKNOWN2;
			return null;
		}
	}
}
